/* 
 *  FileName: globalmem.c - 模拟字符设备操作（书写顺序由下而上）
 *                          使用private_data,驱动可支持2个设备
 *
 *  Author: He Jingbo   jbhe@isoftstone.com
 *
 *  Date: Wed 29 Dec 2010 04:27:20 PM CST
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "globalmem.h"

struct globalmem_dev *globalmem_devp;   /* 设备结构体指针 */
static int globalmem_major = GLOBALMEM_MAJOR;

/* 文件打开函数 */
int globalmem_open(struct inode *inode, struct file *filp)
{
    /* 将设备结构体指针赋值给文件私有数据 */
    struct globalmem_dev *dev;

    /* 
     * 由结构体的成员地址,获得结构体的地址
     * container_of(成员地址,成员所在结构体类型,成员在结构体中的变量名)
     * 从inode中获取单个设备的globalmem_devp,
     * 全局变量globalmem_devp保存的是两个设备结构体的起始地址 */
    dev = container_of(inode->i_cdev, struct globalmem_dev, cdev);
    filp->private_data = dev;
    return 0;
}

/* 文件释放函数 */
int globalmem_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* ioctl()函数接受MEM_CLEAR命令，将全局内存的有效数据长度清零，
 * 对于设备不支持的命令，ioctl()函数返回-EINVAL.
 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, 
        unsigned int cmd, unsigned long arg)
{
    /* 获取设备结构体指针 */
    struct globalmem_dev *dev = filp->private_data;

    switch (cmd)
    {
        case MEM_CLEAR:
            if(down_interruptible(&dev->sem))
            {
                /* 获取信号量失败 */
                return -ERESTARTSYS;
            }
            /* 清零全局内存 */
            memset(dev->mem, 0, GLOBALMEM_SIZE);
            /* 释放信号量 */
            up(&dev->sem);
            printk(KERN_INFO "globalmem is set to zero \n");
            break;

        default:
            return -EINVAL;
    }
    return 0;
}

/* 
 * seek()函数对文件定位的起始地址可以是文件头(SEEK_SEET, 0),
 * 当前位置(SEEK_CUR, 1)和文件尾(SEEK_END, 2),
 * globalmem支持从文件开头和当前位置相对位移
 */

/* 驱动llseek函数实现,用来修改文件当前读写位置 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret;
    switch (orig)
    {
        /* 从文件开头开始偏移 */
        case 0:
            if(offset < 0)
            {
                ret = -EINVAL;
                break;
            }
            if((unsigned int)offset > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }

            filp->f_pos = (unsigned int) offset;
            ret = filp->f_pos;
            break;

            /* 从当前位置开始偏移 */
        case 1:
            if((filp->f_pos + offset) > GLOBALMEM_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            if((filp->f_pos + offset) < 0)
            {
                ret = -EINVAL;
                break;
            }
            
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;

        default:
            ret = -EINVAL;
    }
    return ret;
}

/* 驱动写函数实现 */
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    unsigned long p = *ppos;
    struct globalmem_dev *dev = filp->private_data;

    /* 分析和获取有效的写长度 */
    if(p >= GLOBALMEM_SIZE)
    {
        /* 要写的偏移位置越界 */
        return 0;
    }
    if(count > GLOBALMEM_SIZE -p)
    {
        count = GLOBALMEM_SIZE - p;
    }

    if(down_interruptible(&dev->sem))
    {
        /* 获取信号量失败 */
        return -ERESTARTSYS;
    }

    /* 用户空间->内核空间 */
    if(copy_from_user(dev->mem + p, buf, count))
    {
        ret = -EFAULT;
    }
    else
    {
        *ppos   += count;
        ret      = count;
        printk(KERN_INFO "written %d byte(s) from %ld\n", count, p);
    } 

    /* 释放信号量 */
    up(&dev->sem);
    return ret;
}

/* 驱动读函数实现 */
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    unsigned long p = *ppos;
    struct globalmem_dev *dev = filp->private_data;
    
    /* 分析和获取有效的读长度 */
    if(p >= GLOBALMEM_SIZE)
    {
        /* 要读的偏移位置越界 */
        return 0;
    }
    if(count > GLOBALMEM_SIZE - p)
    {
        /* 要读的字节数太大 */
        count = GLOBALMEM_SIZE -p;
    }

    if(down_interruptible(&dev->sem))
    {
        /* 获取信号量失败 */
        return -ERESTARTSYS;
    }

    /* 从内核空间->用户空间传递数据 */
    if(copy_to_user(buf, (void*)(dev->mem + p), count))
    {
        ret = -EFAULT;
    }
    else
    {
        *ppos   += count;
        ret      = count;
        printk(KERN_INFO "read %d byte(s) from %ld\n", count, p);
    }

    /* 释放信号量 */
    up(&dev->sem);
    return ret;
}

/* 设置设备操作函数组 */
static const struct file_operations globalmem_fops =
{
    .owner  = THIS_MODULE,
    .open   = globalmem_open,
    .read   = globalmem_read,
    .write  = globalmem_write,
    .ioctl  = globalmem_ioctl,
    .llseek = globalmem_llseek,
    .release= globalmem_release,
};

/* 初始化并添加cdev结构体 */
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
    int err;
    dev_t devno = MKDEV(globalmem_major, index);

    cdev_init(&dev->cdev, &globalmem_fops);
    dev->cdev.owner = THIS_MODULE;
    err = cdev_add(&dev->cdev, devno, 1);
    if(err)
    {
        printk(KERN_NOTICE "Error %d adding globalmem", err);
    }
}

/* globalmem 设备驱动模块加载函数 */
int globalmem_init(void)
{
    int result;
    dev_t devno = MKDEV(globalmem_major, 0);

    /* 申请字符设备驱动 */
    if(globalmem_major)
    {
        /* 指定主设备号时，注册设备 */
        result = register_chrdev_region(devno, 1, "globalmem");
    }
    else
    {   
        /* 未指定设备号时，动态分配设备号，并将信息存储在devno中 */
        result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
        globalmem_major = MAJOR(devno);
    }
    if(result < 0)
    {
        return result;
    }

    /* 动态申请设备结构体内存 */
    globalmem_devp = kmalloc(2*sizeof(struct globalmem_dev), GFP_KERNEL);
    if(!globalmem_devp)
    {
        /* 申请失败 */
        result = -ENOMEM;
        goto fail_malloc;
    }
    memset(globalmem_devp, 0, 2*sizeof(struct globalmem_dev));

    /* 分配到系统资源后，设置该设备 */
    globalmem_setup_cdev(&globalmem_devp[0], 0);
    globalmem_setup_cdev(&globalmem_devp[1], 1);
    /* 初始化信号量 */
    init_MUTEX(&globalmem_devp->sem);
    return 0;

fail_malloc:
    /* 因为没有分配到资源,所以不用释放资源 */
    unregister_chrdev_region(devno, 1);
    return  result;
}

/* globalmem设备驱动模块卸载函数 */
void globalmem_exit(void)
{
    cdev_del(&(globalmem_devp[0].cdev));                    /* 删除cdev结构 */
    cdev_del(&(globalmem_devp[1].cdev));                    /* 删除cdev结构 */
    kfree(globalmem_devp);                                  /* 释放设备结构体内存 */
    unregister_chrdev_region(MKDEV(globalmem_major, 0), 2); /* 注销设备 */
}

/* 使用函数，module_param()在模块加载时可以指定变量值
 * 如 insmod globalmem.ko globalmem_major=0
 * 则模块驱动中,globalmem_major的值为0
 */
module_param(globalmem_major, int, S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);

MODULE_AUTHOR("Jacob Ho <jbhe@isoftstone.com>");
MODULE_LICENSE("Dual BSD/GPL");
